﻿using System;
using System.Text;
using SoftwareConsulting.BI11.Studio.InternalServices;
using SoftwareConsulting.BI11.NumericalMethods.Interfaces;
using SoftwareConsulting.BI11.NumericalMethods.MatrixAlgebra;

namespace SoftwareConsulting.BI11.NumericalMethods.Optimization
{
    /// <summary>
    /// Класс симплекс-метода для поиска оптимальных точек многоаргументных функций
    /// </summary>
    public class SimplexAlgorithm : MultiVariableOptimizer
    {	    
        private SuspiciousOptimalVector[] _simplexResult;//наилучшее значение, найденное симплекс-методом

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="function">Функция, требующая решения</param>
        /// <param name="pointsCreator">Фабрика точек</param>
        /// <param name="initialValue">Начальное значение</param>
        public SimplexAlgorithm(IManyVariableFunction function, SuspiciousOptimalPointAndVectorFactory pointsCreator, double[] initialValue) :
            base(function, pointsCreator, initialValue)
        {
        }

        /// <summary>
        /// Добавляет новую лучшую точку
        /// </summary>
        /// <param name="bestVector"></param>
        private void AddBestPoint(SuspiciousOptimalVector bestVector)
        {
            int n = _simplexResult.Length;
            while (--n > 0)
                _simplexResult[n] = _simplexResult[n - 1];
            _simplexResult[0] = bestVector;
        }
        
        /// <summary>
        /// Добавляет стяг
        /// </summary>
        /// <param name="summitWithContractedMedian">Вершина, чья медиана стянута</param>
        /// <returns>true - найдена точка лучше, false - не найдена</returns>
        private bool AddContraction(Vector summitWithContractedMedian)
        {
            summitWithContractedMedian.Add(_simplexResult[GetResult().Length].Argument);
            summitWithContractedMedian.Scale(0.5);
            SuspiciousOptimalVector contractedPoint = PointsFactory.CreateVector(summitWithContractedMedian, Function);
            if (contractedPoint.BetterThan(_simplexResult[0]))
            {
                AddBestPoint(contractedPoint);
                return true;
            }
            return false;
        }
        
        /// <summary>
        /// Добавляет отражение
        /// </summary>
        /// <param name="gravityCenter">точка - центр тяжести</param>
        /// <returns>true - найдена точка лучше, false - не найдена</returns>
        private bool AddReflection(Vector gravityCenter)
        {
            Vector reflectedVector = gravityCenter * 2;
            reflectedVector.Subtract(_simplexResult[GetResult().Length].Argument);
            SuspiciousOptimalVector reflectedPoint = PointsFactory.CreateVector(reflectedVector, Function);
            if (reflectedPoint.BetterThan(_simplexResult[0]))
            {
                reflectedVector.Scale(2);
                reflectedVector.Subtract(gravityCenter);
                SuspiciousOptimalVector expandedPoint = PointsFactory.CreateVector(reflectedVector, Function);
                if (expandedPoint.BetterThan(reflectedPoint))
                    AddBestPoint(expandedPoint);
                else
                    AddBestPoint(reflectedPoint);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Центр тяжести лучших точек симплекс-метода, исключая самую худшую
        /// </summary>
        /// <returns></returns>
        private Vector GravityCenter()
        {
            Vector g = new Vector(GetResult().Length);
            for (int i = 0; i < GetResult().Length; i++)
                g.Add(_simplexResult[i].Argument);
            g.Scale(1.0 / GetResult().Length);
            return g;
        }

        /// <summary>
        /// Возвращает максимальную погрешность симплекс-метода в каждом из направлений
        /// </summary>
        /// <returns></returns>
        private double ComputeRelativeError()
        {            
            double[] position = _simplexResult[0].Argument;
            double[] min = new double[position.Length];
            double[] max = new double[position.Length];
            position.CopyTo(min, 0);
            position.CopyTo(max, 0);
            for (int j = 1; j < _simplexResult.Length; j++)
            {
                position = _simplexResult[j].Argument;
                for (int i = 0; i < position.Length; i++)
                {
                    min[i] = Math.Min(min[i], position[i]);
                    max[i] = Math.Max(max[i], position[i]);
                }
            }
            double eps = 0;
            for (int i = 1; i < position.Length; i++)
                eps = Math.Max(eps, this.GetRelativeError(max[i] - min[i], GetResult()[i]));
            return eps;
        }

        /// <summary>
        /// Сокращает симплекс от лучшей точки
        /// </summary>
        private void ContractSimplex()
        {
            double[] bestPoint = _simplexResult[0].Argument;
            for (int i = 1; i < _simplexResult.Length; i++)
                _simplexResult[i].ContractFrom(bestPoint);
            SortPoints(_simplexResult);
        }

        /// <summary>
        /// Выполняет итерацию
        /// </summary>
        /// <returns>наибольшая погрешность в каждом из направлений симплекс-метода на данной итерации</returns>        
        public override double DoIteration()
        {
            try
            {
                double bestValue = _simplexResult[0].Value;
                Vector g = GravityCenter();
                if (!AddReflection(g))
                {
                    if (!AddContraction(g))
                        ContractSimplex();
                }
                SetInitialValue(_simplexResult[0].Argument);
                return ComputeRelativeError();
            }
            catch (BIMatrixIllegalDimensionException) 
            { 
                return 1; 
            };
        }
        
        /// <summary>
        /// Создает случайную популяцию
        /// </summary>
        /// <remarks>ищет оптимальную точку в каждом направлении, начиная с начального значения</remarks>
        public override void CreateRandomPopulation()
        {
            double[] v = new double[GetResult().Length];
            for (int i = 0; i < GetResult().Length; i++)
                v[i] = 0;
            VectorProjectedFunction projection = new VectorProjectedFunction(Function, GetResult(), v);
            OneVariableOptimizer unidimensionalFinder = new OneVariableOptimizer(projection, PointsFactory);
            unidimensionalFinder.SetDesiredPrecision(DesiredPrecision);
            _simplexResult = new SuspiciousOptimalVector[GetResult().Length + 1];
            try
            {
                for (int i = 0; i < GetResult().Length; i++)
                {
                    v[i] = 1;
                    projection.SetDirection(v);
                    v[i] = 0;
                    unidimensionalFinder.InitialBestZeroApproximation = 0;
                    unidimensionalFinder.Evaluate();
                    _simplexResult[i] = PointsFactory.CreateVector(projection.GetOriginShiftedBy(unidimensionalFinder.BestZeroApproximation), Function);
                }
            }
            catch (BIMatrixIllegalDimensionException ex)
            {
                Trace.StartBlock("BIMatrixIllegalDimensionException killed");
                Trace.Message("BIMatrixIllegalDimensionException catched and silently killed! Exception text:");
                Trace.Message(ex.ToString());
                Trace.EndBlock();
            };
            _simplexResult[GetResult().Length] = PointsFactory.CreateVector(GetResult(), Function);
            SortPoints(_simplexResult);
        }

        /// <summary>
        /// Возвращает как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(_simplexResult[0]);
            for (int i = 1; i < _simplexResult.Length; i++)
            {
                sb.Append('\n');
                sb.Append(_simplexResult[i]);
            }
            return sb.ToString();
        }
    }
}
